﻿//-----------------------------------------------------------------------
// <copyright file="MachineDataContract.cs" company="CodePlex">
//     Copyright (c) Doug Holland, CodePlex. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace CodePlex.Diagnostics.Contracts
{
    using System;
    
    using System.Collections.Generic;
    using System.Collections.ObjectModel;

    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    
    using System.Globalization;
    
    using System.Management;
    
    using System.Net;
    
    using System.Runtime.Serialization;

    /// <summary>
    /// Defines the data contract representing the details of a machine.
    /// </summary>
    [DataContract(Name = "MachineDataContract", Namespace = "http://schemas.datacontract.org/2004/07/CodePlex.Diagnostics.Contracts")]
    public sealed class MachineDataContract : IExtensibleDataObject
    {
        #region public MachineDataContract()

        /// <summary>
        /// Initializes a new instance of the <see cref="T:MachineDataContract"/> class.
        /// </summary>
        public MachineDataContract()
        {
            this.MachineName = Environment.MachineName;

            // retrieve details about the installed memory modules.
            IList<WmiMemoryModule> modules = WmiMemoryModule.GetMemoryModules();

            int speed = modules[0].Speed;

            long capacity = 0;

            foreach (WmiMemoryModule module in modules)
            {
                // calculate the total capacity of installed memory modules.
                capacity += module.Capacity;

                // in cases where memory speed is not the same the slowest memory will be used.
                if (speed > module.Speed)
                {
                    speed = module.Speed;
                }
            }

            this.InstalledMemory = capacity;

            this.InstalledMemorySpeed = speed;

            WmiOperatingSystem operatingSystem = new WmiOperatingSystem();

            this.OperatingSystem = operatingSystem.Name;

            this.ProcessorCount = Environment.ProcessorCount;

            // retrieve a list of IP addresses for the given machine name.
            IPHostEntry entry = Dns.GetHostEntry(Environment.MachineName);

            // iterate through the IP addresses for an appropriate IP address, the iteration is done in reverse because the first address is usually the local address '::1' which isn't very useful.
            for (int index = entry.AddressList.Length - 1; index >= 0; index--)
            {
                this.IPAddress = entry.AddressList[index].ToString();

                // ensure the address we've got isn't the local '::1' address and if there are more addresses contine the loop, otherwise break.
                if (string.Equals(this.IPAddress.ToString(), "::1") && index > 0)
                {
                    continue;
                }
                else
                {
                    break;
                }
            }
        }

        #endregion

        #region public long? InstalledMemory

        /// <summary>
        /// Gets the total memory in bytes installed within the machine.
        /// </summary>
        /// <value>The total memory in bytes installed within the machine.</value>
        [DataMember]
        public long? InstalledMemory
        {
            get;
            private set;
        }

        #endregion

        #region public int? InstalledMemorySpeed

        /// <summary>
        /// Gets the speed of the slowest memory module installed within the machine.
        /// </summary>
        /// <value>The speed of the slowest memory module installed within the machine.</value>
        [DataMember]
        public int? InstalledMemorySpeed
        {
            get;
            private set;
        }

        #endregion

        #region public string IPAddress

        /// <summary>
        /// Gets the IP address of the machine the associated process is running on.
        /// </summary>
        /// <value>The IP address of the machine the associated process is running on.</value>
        [DataMember]
        public string IPAddress
        {
            get;
            private set;
        }

        #endregion

        #region public string OperatingSystem

        /// <summary>
        /// Gets the name of the operating system the associated process is running upon.
        /// </summary>
        /// <value>The name of the operating system the associated process is running upon.</value>
        [DataMember]
        public string OperatingSystem
        {
            get;
            private set;
        }

        #endregion

        #region public string MachineName

        /// <summary>
        /// Gets the name of the machine the associated process is running upon.
        /// </summary>
        /// <value>The name of the machine that the associated process is running upon.</value>
        [DataMember]
        public string MachineName
        {
            get;
            private set;
        }

        #endregion

        #region public int ProcessorCount

        /// <summary>
        /// Gets the number of logical processors on the machine.
        /// </summary>
        /// <value>The number of logical processors on the machine.</value>
        [DataMember]
        public int ProcessorCount
        {
            get;
            private set;
        }

        #endregion

        #region ExtensionDataObject IExtensibleDataObject.ExtensionData

        /// <summary>
        /// Gets or sets the structure that contains extra data.
        /// </summary>
        /// <value>An <see cref="T:System.Runtime.Serialization.ExtensionDataObject"/> that contains data that is not recognized as belonging to the data contract.</value>
        ExtensionDataObject IExtensibleDataObject.ExtensionData
        {
            get;
            set;
        }

        #endregion

        #region internal sealed class WmiOperatingSystem

        /// <summary>
        /// Describes the operating system executing upon the given machine.
        /// </summary>
        internal sealed class WmiOperatingSystem
        {
            #region public WmiOperatingSystem()

            /// <summary>
            /// Initializes a new instance of the <see cref="T:WmiOperatingSystem"/> class.
            /// </summary>
            public WmiOperatingSystem()
            {
                ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\CIMV2", "SELECT * FROM Win32_OperatingSystem");

                foreach (ManagementObject managementObject in searcher.Get())
                {
                    string name = managementObject["Name"] as string;

                    this.Name = name.Substring(0, name.IndexOf('|'));
                }
            }

            #endregion

            #region public string Name

            /// <summary>
            /// Gets the name of the operating system currently running upon the machine.
            /// </summary>
            /// <value>The name of the operating system currently running upon the machine.</value>
            public string Name
            {
                get;
                private set;
            }

            #endregion
        }

        #endregion

        #region internal sealed class WmiMemoryModule

        /// <summary>
        /// Describes the memory modules installed upon the given machine.
        /// </summary>
        internal sealed class WmiMemoryModule
        {
            #region WmiMemoryModule Private Fields

            /// <summary>
            /// Constant used by WMI to identify the system ROM.
            /// </summary>
            private const string SystemRom = "SYSTEM ROM";

            #endregion

            #region private WmiMemoryModule()

            /// <summary>
            /// Prevents a default instance of the <see cref="T:WmiMemoryModule"/> class from being created.    
            /// </summary>
            private WmiMemoryModule()
            {
            }

            #endregion

            #region public long Capacity

            /// <summary>
            /// Gets the amount of memory in bytes for the memory module.
            /// </summary>
            /// <value>The amount of memory in bytes for the memory module.</value>
            public long Capacity
            {
                get;
                private set;
            }

            #endregion

            #region public int Speed

            /// <summary>
            /// Gets the speed of the memory module.
            /// </summary>
            /// <value>The speed of the memory module.</value>
            public int Speed
            {
                get;
                private set;
            }

            #endregion

            #region public static IList<WmiMemoryModule> GetMemoryModules()

            /// <summary>
            /// Gets an <see cref="T:System.Collections.Generic.IList`1"/> containin a <see cref="T:MemoryModule"/> instance for each physical memory module.
            /// </summary>
            /// <remarks>
            /// The <see cref="M:MemoryModule.GetMemoryModules"/> method is implemented as a method and not a property because the list of memory modules will take considerably
            /// longer to retrieve than a simple field. Static code analysis warning CA1024 is therefore suppressed after consulting the .NET Framework Design Guidelines written by
            /// Brad Abrams and Krzysztof Cwalina which is published by Addison Wesley.
            /// </remarks>
            /// <returns>An <see cref="T:System.Collections.Generic.IList`1"/> containin a <see cref="T:MemoryModule"/> instance for each physical memory module.</returns>
            public static IList<WmiMemoryModule> GetMemoryModules()
            {
                try
                {
                    List<WmiMemoryModule> modules = new List<WmiMemoryModule>();

                    ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\CIMV2", "SELECT * FROM Win32_PhysicalMemory");

                    foreach (ManagementObject managementObject in searcher.Get())
                    {
                        WmiMemoryModule module = new WmiMemoryModule();

                        string moduleId = managementObject["DeviceLocator"] as string;

                        if (string.Equals(moduleId, SystemRom))
                        {
                            // ignore System ROM memory.
                            continue;
                        }

                        module.Capacity = Convert.ToInt64(managementObject["Capacity"], CultureInfo.CurrentCulture);
                        module.Speed = Convert.ToInt32(managementObject["Speed"], CultureInfo.CurrentCulture);

                        modules.Add(module);
                    }

                    return new ReadOnlyCollection<WmiMemoryModule>(modules);
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.Message);
                    Trace.WriteLine(ex.StackTrace);

                    throw;
                }
            } 

            #endregion
        }

        #endregion
    }
}
